﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.IO;

namespace SynoManager
{
    /// <summary>
    /// Class that represents a connection to a Synology Device (DiskStation, CubeStation or RackStation).
    /// </summary>
    public class SynoDevice
    {
        private string address;
        private string username;
        private string password;
        private bool preferSecure = false;
        private int port = 5000;
        private int securePort = 5001;

        /// <summary>
        /// Create a new <c>SynoDevice</c> instance.
        /// </summary>
        /// <param name="address">Hostname or IP-Address of the device.</param>
        /// <param name="username">Username used to connect to the device.</param>
        /// <param name="password">Password used to connect to the device.</param>
        /// <param name="port">Main HTTP communication port</param>
        /// <param name="securePort">Secure Https communication port</param>
        /// <param name="preferSecure">Prefer https over http</param>
        public SynoDevice(string address, string username, string password, int port, int securePort, bool preferSecure)
        {
            string fullAddress = address;

            this.address = address;
            this.username = username;
            this.password = password;
            this.port = port;
            this.securePort = securePort;
            this.preferSecure = preferSecure;

            // Enumerates available services (initializing is done in the open function)
            LoadServices();
        }

        #region Open/Close
        private bool isOpen = false;
        /// <summary>
        /// Open the connection to the device, initialise all services.
        /// </summary>
        public void Open()
        {
            Debug.WriteLine("Opening connection to SynoDevice at " + address + ((preferSecure) ? " (Secure)":""), "SynoDevice");
            CheckIfConnectionIsClosed();
            foreach(var service in services)
            {
                Debug.WriteLine("Initializing service " + service.GetType().Name, "SynoDevice");
                service.Initialize(this);
            }

            isOpen = true;
            Debug.WriteLine("Connection to SynoDevice is now open", "SynoDevice");
        }

        /// <summary>
        /// Close the connection to the device, also closing all services.
        /// </summary>
        public void Close()
        {
            Debug.WriteLine("Closing connection to SynoDevice at " + address, "SynoDevice");
            CheckIfConnectionIsOpen();
            foreach(var service in services)
            {
                Debug.WriteLine("Closing service " + service.GetType().Name, "SynoDevice");
                service.Close();
            }
            isOpen = false;
            Debug.WriteLine("Connection to SynoDevice is now closed", "SynoDevice");
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the Hostname or IP-Adress of the device.
        /// </summary>
        /// <remarks>Can only be set if the connection is closed</remarks>
        public string Address
        {
            get { return address; }
            set
            {
                CheckIfConnectionIsClosed();
                address = value;
            }
        }

        public int Port
        {
            get { return port; }
            set
            {
                CheckIfConnectionIsClosed();
                port = value;
            }
        }

        /// <summary>
        /// Gets or sets the Username used to connect to the device.
        /// </summary>
        /// <remarks>Can only be set if the connection is closed</remarks>
        public string Username
        {
            get { return username; }
            set
            {
                CheckIfConnectionIsClosed();
                username = value;
            }
        }

        /// <summary>
        /// Gets or sets the Password used to connect to the device.
        /// </summary>
        /// <remarks>Can only be set if the connection is closed</remarks>
        public string Password
        {
            get { return password; }
            set 
            {
                CheckIfConnectionIsClosed();
                password = value; 
            }
        }

        public bool PreferSecure
        {
            get { return preferSecure; }
            set
            {
                CheckIfConnectionIsClosed();
                preferSecure = value;
            }
        }

        public int SecurePort
        {
            get { return securePort; }
            set
            {
                CheckIfConnectionIsClosed();
                securePort = value;
            }
        }

        /// <summary>
        /// Gets wether the connection to the device is open.
        /// </summary>
        public bool IsOpen
        {
            get { return isOpen; }
        }

        /// <summary>
        /// Gets wether the currently connected user is an administrator
        /// </summary>
        public bool IsAdmin
        {
            get { return username.Equals("admin", StringComparison.InvariantCultureIgnoreCase); }
        }
        #endregion

        #region Connection Check Functions
        /// <summary>
        /// Simple check function that throws an exception if the connection is closed.
        /// </summary>
        void CheckIfConnectionIsOpen()
        {
            if (!isOpen)
                throw new Exception("There is no open connection to the device");
        }

        /// <summary>
        /// Simple check function that throws an exception if the connection is open.
        /// </summary>
        void CheckIfConnectionIsClosed()
        {
            if (isOpen)
                throw new Exception("First close the connection to the device");
        }
        #endregion

        #region Connection State Functions
        public event EventHandler ConnectionStateChanged;
        private bool isConnected;
        public bool IsConnected
        {
            get { return isConnected;}
            set
            {
                bool oldValue = isConnected;
                isConnected = value;
                if (oldValue != isConnected)
                {
                    Debug.WriteLine("Device connection state changed to: " + isConnected, "SynoDevice");

                    if (ConnectionStateChanged != null)
                        ConnectionStateChanged(this, EventArgs.Empty);
                }
            }
        }
        #endregion

        #region Services

        /// <summary>
        /// Builds the list of services
        /// </summary>
        private void LoadServices()
        {
            services.Clear();

            Debug.WriteLine("Loading all dll assemblies into AppDomain");
            foreach(string path in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "*.dll"))
                Assembly.LoadFile(path);

            Debug.WriteLine("Searching for services...");
            foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        if ((!type.IsAbstract) && (!type.IsInterface) &&
                            (type.GetInterface(typeof (IService).Name) != null))
                        {
                            IService service = (IService) Activator.CreateInstance(type);
                            services.Add(service);
                        }
                    }
                }
                catch (ReflectionTypeLoadException ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
            Debug.Assert(services.Count > 0, "No services found");
            Debug.WriteLine(services.Count + " services found", "SynoDevice");
        }

        private List<IService> services = new List<IService>();

        /// <summary>
        /// Gets the list of services.
        /// </summary>
        public IEnumerable<IService> Services
        {
            get
            {
                return services.ToArray();
            }
        }

        /// <summary>
        /// Find the service of a specific type.
        /// </summary>
        /// <typeparam name="T">Type of service to find.</typeparam>
        /// <returns>Instance of the service.</returns>
        public T FindService<T>()
        {
            return services.OfType<T>().First();
        }
        #endregion
    }
}
